package com.copplay.system.service;

import cn.dev33.satoken.secure.BCrypt;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.RandomUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.copplay.system.domain.*;
import com.copplay.system.mapper.*;
import com.copplay.system.web.bo.TenantBo;
import com.copplay.system.web.vo.TenantVo;
import lombok.RequiredArgsConstructor;
import com.copplay.common.core.constant.CacheNames;
import com.copplay.common.core.constant.Constants;
import com.copplay.common.core.constant.SystemConstants;
import com.copplay.common.core.constant.TenantConstants;
import com.copplay.common.core.exception.ServiceException;
import com.copplay.common.core.service.WorkflowService;
import com.copplay.common.core.utils.MapstructUtils;
import com.copplay.common.core.utils.SpringUtils;
import com.copplay.common.core.utils.StreamUtils;
import com.copplay.common.core.utils.StringUtils;
import com.copplay.common.mybatis.core.page.PageQuery;
import com.copplay.common.mybatis.core.page.TableDataInfo;
import com.copplay.common.redis.utils.CacheUtils;
import com.copplay.common.tenant.core.TenantEntity;
import com.copplay.common.tenant.helper.TenantHelper;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;

/**
 * 租户Service业务层处理
 *
 * @author Michelle.Chung
 */
@RequiredArgsConstructor
@Service
public class SysTenantServiceImpl implements SysTenantService {

    private final TenantMapper baseMapper;
    private final TenantPackageMapper tenantPackageMapper;
    private final UserMapper userMapper;
    private final DeptMapper deptMapper;
    private final RoleMapper roleMapper;
    private final RoleMenuMapper roleMenuMapper;
    private final RoleDeptMapper roleDeptMapper;
    private final UserRoleMapper userRoleMapper;
    private final DictTypeMapper dictTypeMapper;
    private final DictDataMapper dictDataMapper;
    private final ConfigMapper configMapper;

    /**
     * 查询租户
     */
    @Override
    public TenantVo queryById(Long id) {
        return baseMapper.selectVoById(id);
    }

    /**
     * 基于租户ID查询租户
     */
    @Cacheable(cacheNames = CacheNames.TENANT, key = "#tenantId")
    @Override
    public TenantVo queryByTenantId(String tenantId) {
        return baseMapper.selectVoOne(new LambdaQueryWrapper<Tenant>().eq(Tenant::getTenantId, tenantId));
    }

    /**
     * 查询租户列表
     */
    @Override
    public TableDataInfo<TenantVo> queryPageList(TenantBo bo, PageQuery pageQuery) {
        LambdaQueryWrapper<Tenant> lqw = buildQueryWrapper(bo);
        Page<TenantVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
        return TableDataInfo.build(result);
    }

    /**
     * 查询租户列表
     */
    @Override
    public List<TenantVo> queryList(TenantBo bo) {
        LambdaQueryWrapper<Tenant> lqw = buildQueryWrapper(bo);
        return baseMapper.selectVoList(lqw);
    }

    private LambdaQueryWrapper<Tenant> buildQueryWrapper(TenantBo bo) {
        LambdaQueryWrapper<Tenant> lqw = Wrappers.lambdaQuery();
        lqw.eq(StringUtils.isNotBlank(bo.getTenantId()), Tenant::getTenantId, bo.getTenantId());
        lqw.like(StringUtils.isNotBlank(bo.getContactUserName()), Tenant::getContactUserName, bo.getContactUserName());
        lqw.eq(StringUtils.isNotBlank(bo.getContactPhone()), Tenant::getContactPhone, bo.getContactPhone());
        lqw.like(StringUtils.isNotBlank(bo.getCompanyName()), Tenant::getCompanyName, bo.getCompanyName());
        lqw.eq(StringUtils.isNotBlank(bo.getLicenseNumber()), Tenant::getLicenseNumber, bo.getLicenseNumber());
        lqw.eq(StringUtils.isNotBlank(bo.getAddress()), Tenant::getAddress, bo.getAddress());
        lqw.eq(StringUtils.isNotBlank(bo.getIntro()), Tenant::getIntro, bo.getIntro());
        lqw.like(StringUtils.isNotBlank(bo.getDomain()), Tenant::getDomain, bo.getDomain());
        lqw.eq(bo.getPackageId() != null, Tenant::getPackageId, bo.getPackageId());
        lqw.eq(bo.getExpireTime() != null, Tenant::getExpireTime, bo.getExpireTime());
        lqw.eq(bo.getAccountCount() != null, Tenant::getAccountCount, bo.getAccountCount());
        lqw.eq(StringUtils.isNotBlank(bo.getStatus()), Tenant::getStatus, bo.getStatus());
        lqw.orderByAsc(Tenant::getId);
        return lqw;
    }

    /**
     * 新增租户
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean insertByBo(TenantBo bo) {
        Tenant add = MapstructUtils.convert(bo, Tenant.class);

        // 获取所有租户编号
        List<String> tenantIds = baseMapper.selectObjs(
            new LambdaQueryWrapper<Tenant>().select(Tenant::getTenantId), x -> {
                return Convert.toStr(x);
            });
        String tenantId = generateTenantId(tenantIds);
        add.setTenantId(tenantId);
        boolean flag = baseMapper.insert(add) > 0;
        if (!flag) {
            throw new ServiceException("创建租户失败");
        }
        bo.setId(add.getId());

        // 根据套餐创建角色
        Long roleId = createTenantRole(tenantId, bo.getPackageId());

        // 创建部门: 公司名是部门名称
        Dept dept = new Dept();
        dept.setTenantId(tenantId);
        dept.setDeptName(bo.getCompanyName());
        dept.setParentId(Constants.TOP_PARENT_ID);
        dept.setAncestors(Constants.TOP_PARENT_ID.toString());
        deptMapper.insert(dept);
        Long deptId = dept.getDeptId();

        // 角色和部门关联表
        RoleDept roleDept = new RoleDept();
        roleDept.setRoleId(roleId);
        roleDept.setDeptId(deptId);
        roleDeptMapper.insert(roleDept);

        // 创建系统用户
        User user = new User();
        user.setTenantId(tenantId);
        user.setUserName(bo.getUsername());
        user.setNickName(bo.getUsername());
        user.setPassword(BCrypt.hashpw(bo.getPassword()));
        user.setDeptId(deptId);
        userMapper.insert(user);
        //新增系统用户后，默认当前用户为部门的负责人
        Dept sd = new Dept();
        sd.setLeader(user.getUserId());
        sd.setDeptId(deptId);
        deptMapper.updateById(sd);

        // 用户和角色关联表
        UserRole userRole = new UserRole();
        userRole.setUserId(user.getUserId());
        userRole.setRoleId(roleId);
        userRoleMapper.insert(userRole);

        String defaultTenantId = TenantConstants.DEFAULT_TENANT_ID;
        List<DictType> dictTypeList = dictTypeMapper.selectList(
            new LambdaQueryWrapper<DictType>().eq(DictType::getTenantId, defaultTenantId));
        List<DictData> dictDataList = dictDataMapper.selectList(
            new LambdaQueryWrapper<DictData>().eq(DictData::getTenantId, defaultTenantId));
        for (DictType dictType : dictTypeList) {
            dictType.setDictId(null);
            dictType.setTenantId(tenantId);
            dictType.setCreateDept(null);
            dictType.setCreateBy(null);
            dictType.setCreateTime(null);
            dictType.setUpdateBy(null);
            dictType.setUpdateTime(null);
        }
        for (DictData dictData : dictDataList) {
            dictData.setDictCode(null);
            dictData.setTenantId(tenantId);
            dictData.setCreateDept(null);
            dictData.setCreateBy(null);
            dictData.setCreateTime(null);
            dictData.setUpdateBy(null);
            dictData.setUpdateTime(null);
        }
        dictTypeMapper.insertBatch(dictTypeList);
        dictDataMapper.insertBatch(dictDataList);

        List<Config> configList = configMapper.selectList(
            new LambdaQueryWrapper<Config>().eq(Config::getTenantId, defaultTenantId));
        for (Config config : configList) {
            config.setConfigId(null);
            config.setTenantId(tenantId);
            config.setCreateDept(null);
            config.setCreateBy(null);
            config.setCreateTime(null);
            config.setUpdateBy(null);
            config.setUpdateTime(null);
        }
        configMapper.insertBatch(configList);

        // 未开启工作流不执行下方操作
        if (SpringUtils.getProperty("warm-flow.enabled", Boolean.class, false)) {
            WorkflowService workflowService = SpringUtils.getBean(WorkflowService.class);
            // 新增租户流程定义
            workflowService.syncDef(tenantId);
        }
        return true;
    }

    /**
     * 生成租户id
     *
     * @param tenantIds 已有租户id列表
     * @return 租户id
     */
    private String generateTenantId(List<String> tenantIds) {
        // 随机生成6位
        String numbers = RandomUtil.randomNumbers(6);
        // 判断是否存在，如果存在则重新生成
        if (tenantIds.contains(numbers)) {
            return generateTenantId(tenantIds);
        }
        return numbers;
    }

    /**
     * 根据租户菜单创建租户角色
     *
     * @param tenantId  租户编号
     * @param packageId 租户套餐id
     * @return 角色id
     */
    private Long createTenantRole(String tenantId, Long packageId) {
        // 获取租户套餐
        TenantPackage tenantPackage = tenantPackageMapper.selectById(packageId);
        if (ObjectUtil.isNull(tenantPackage)) {
            throw new ServiceException("套餐不存在");
        }
        // 获取套餐菜单id
        List<Long> menuIds = StringUtils.splitTo(tenantPackage.getMenuIds(), Convert::toLong);

        // 创建角色
        Role role = new Role();
        role.setTenantId(tenantId);
        role.setRoleName(TenantConstants.TENANT_ADMIN_ROLE_NAME);
        role.setRoleKey(TenantConstants.TENANT_ADMIN_ROLE_KEY);
        role.setRoleSort(1);
        role.setStatus(SystemConstants.NORMAL);
        roleMapper.insert(role);
        Long roleId = role.getRoleId();

        // 创建角色菜单
        List<RoleMenu> roleMenus = new ArrayList<>(menuIds.size());
        menuIds.forEach(menuId -> {
            RoleMenu roleMenu = new RoleMenu();
            roleMenu.setRoleId(roleId);
            roleMenu.setMenuId(menuId);
            roleMenus.add(roleMenu);
        });
        roleMenuMapper.insertBatch(roleMenus);

        return roleId;
    }

    /**
     * 修改租户
     */
    @CacheEvict(cacheNames = CacheNames.TENANT, key = "#bo.tenantId")
    @Override
    public Boolean updateByBo(TenantBo bo) {
        Tenant tenant = MapstructUtils.convert(bo, Tenant.class);
        tenant.setTenantId(null);
        tenant.setPackageId(null);
        return baseMapper.updateById(tenant) > 0;
    }

    /**
     * 修改租户状态
     *
     * @param bo 租户信息
     * @return 结果
     */
    @CacheEvict(cacheNames = CacheNames.TENANT, key = "#bo.tenantId")
    @Override
    public int updateTenantStatus(TenantBo bo) {
        Tenant tenant = new Tenant();
        tenant.setId(bo.getId());
        tenant.setStatus(bo.getStatus());
        return baseMapper.updateById(tenant);
    }

    /**
     * 校验租户是否允许操作
     *
     * @param tenantId 租户ID
     */
    @Override
    public void checkTenantAllowed(String tenantId) {
        if (ObjectUtil.isNotNull(tenantId) && TenantConstants.DEFAULT_TENANT_ID.equals(tenantId)) {
            throw new ServiceException("不允许操作管理租户");
        }
    }

    /**
     * 批量删除租户
     */
    @CacheEvict(cacheNames = CacheNames.TENANT, allEntries = true)
    @Override
    public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
        if (isValid) {
            // 做一些业务上的校验,判断是否需要校验
            if (ids.contains(TenantConstants.SUPER_ADMIN_ID)) {
                throw new ServiceException("超管租户不能删除");
            }
        }
        return baseMapper.deleteByIds(ids) > 0;
    }

    /**
     * 校验企业名称是否唯一
     */
    @Override
    public boolean checkCompanyNameUnique(TenantBo bo) {
        boolean exist = baseMapper.exists(new LambdaQueryWrapper<Tenant>()
            .eq(Tenant::getCompanyName, bo.getCompanyName())
            .ne(ObjectUtil.isNotNull(bo.getTenantId()), Tenant::getTenantId, bo.getTenantId()));
        return !exist;
    }

    /**
     * 校验账号余额
     */
    @Override
    public boolean checkAccountBalance(String tenantId) {
        TenantVo tenant = SpringUtils.getAopProxy(this).queryByTenantId(tenantId);
        // 如果余额为-1代表不限制
        if (tenant.getAccountCount() == -1) {
            return true;
        }
        Long userNumber = userMapper.selectCount(new LambdaQueryWrapper<>());
        // 如果余额大于0代表还有可用名额
        return tenant.getAccountCount() - userNumber > 0;
    }

    /**
     * 校验有效期
     */
    @Override
    public boolean checkExpireTime(String tenantId) {
        TenantVo tenant = SpringUtils.getAopProxy(this).queryByTenantId(tenantId);
        // 如果未设置过期时间代表不限制
        if (ObjectUtil.isNull(tenant.getExpireTime())) {
            return true;
        }
        // 如果当前时间在过期时间之前则通过
        return new Date().before(tenant.getExpireTime());
    }

    /**
     * 同步租户套餐
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean syncTenantPackage(String tenantId, Long packageId) {
        TenantPackage tenantPackage = tenantPackageMapper.selectById(packageId);
        List<Role> roles = roleMapper.selectList(
            new LambdaQueryWrapper<Role>().eq(Role::getTenantId, tenantId));
        List<Long> roleIds = new ArrayList<>(roles.size() - 1);
        List<Long> menuIds = StringUtils.splitTo(tenantPackage.getMenuIds(), Convert::toLong);
        roles.forEach(item -> {
            if (TenantConstants.TENANT_ADMIN_ROLE_KEY.equals(item.getRoleKey())) {
                List<RoleMenu> roleMenus = new ArrayList<>(menuIds.size());
                menuIds.forEach(menuId -> {
                    RoleMenu roleMenu = new RoleMenu();
                    roleMenu.setRoleId(item.getRoleId());
                    roleMenu.setMenuId(menuId);
                    roleMenus.add(roleMenu);
                });
                roleMenuMapper.delete(new LambdaQueryWrapper<RoleMenu>().eq(RoleMenu::getRoleId, item.getRoleId()));
                roleMenuMapper.insertBatch(roleMenus);
            } else {
                roleIds.add(item.getRoleId());
            }
        });
        if (!roleIds.isEmpty()) {
            roleMenuMapper.delete(
                new LambdaQueryWrapper<RoleMenu>().in(RoleMenu::getRoleId, roleIds).notIn(!menuIds.isEmpty(), RoleMenu::getMenuId, menuIds));
        }
        return true;
    }

    /**
     * 同步租户字典
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void syncTenantDict() {
        // 查询超管 所有字典数据
        List<DictType> dictTypeList = new ArrayList<>();
        List<DictData> dictDataList = new ArrayList<>();
        TenantHelper.ignore(() -> {
            dictTypeList.addAll(dictTypeMapper.selectList());
            dictDataList.addAll(dictDataMapper.selectList());
        });
        Map<String, List<DictType>> typeMap = StreamUtils.groupByKey(dictTypeList, TenantEntity::getTenantId);
        Map<String, Map<String, List<DictData>>> typeDataMap = StreamUtils.groupBy2Key(
            dictDataList, TenantEntity::getTenantId, DictData::getDictType);
        // 管理租户字典数据
        List<DictType> defaultTypeMap = typeMap.get(TenantConstants.DEFAULT_TENANT_ID);
        Map<String, List<DictData>> defaultTypeDataMap = typeDataMap.get(TenantConstants.DEFAULT_TENANT_ID);

        // 获取所有租户编号
        List<String> tenantIds = baseMapper.selectObjs(
            new LambdaQueryWrapper<Tenant>().select(Tenant::getTenantId)
                .eq(Tenant::getStatus, SystemConstants.NORMAL), x -> {
                return Convert.toStr(x);
            });
        List<DictType> saveTypeList = new ArrayList<>();
        List<DictData> saveDataList = new ArrayList<>();
        Set<String> set = new HashSet<>();
        for (String tenantId : tenantIds) {
            if (TenantConstants.DEFAULT_TENANT_ID.equals(tenantId)) {
                continue;
            }
            for (DictType dictType : defaultTypeMap) {
                List<String> typeList = StreamUtils.toList(typeMap.get(tenantId), DictType::getDictType);
                List<DictData> dataList = defaultTypeDataMap.get(dictType.getDictType());
                if (typeList.contains(dictType.getDictType())) {
                    List<DictData> dataListTenant = typeDataMap.get(tenantId).get(dictType.getDictType());
                    Map<String, DictData> map = StreamUtils.toIdentityMap(dataListTenant, DictData::getDictValue);
                    for (DictData dictData : dataList) {
                        if (!map.containsKey(dictData.getDictValue())) {
                            DictData data = BeanUtil.toBean(dictData, DictData.class);
                            // 设置字典编码为 null
                            data.setDictCode(null);
                            data.setTenantId(tenantId);
                            data.setCreateTime(null);
                            data.setUpdateTime(null);
                            data.setCreateDept(null);
                            data.setCreateBy(null);
                            data.setUpdateBy(null);
                            set.add(tenantId);
                            saveDataList.add(data);
                        }
                    }
                } else {
                    DictType type = BeanUtil.toBean(dictType, DictType.class);
                    type.setDictId(null);
                    type.setTenantId(tenantId);
                    type.setCreateTime(null);
                    type.setUpdateTime(null);
                    set.add(tenantId);
                    saveTypeList.add(type);
                    if (CollUtil.isNotEmpty(dataList)) {
                        // 筛选出 dictType 对应的 data
                        for (DictData dictData : dataList) {
                            DictData data = BeanUtil.toBean(dictData, DictData.class);
                            // 设置字典编码为 null
                            data.setDictCode(null);
                            data.setTenantId(tenantId);
                            data.setCreateTime(null);
                            data.setUpdateTime(null);
                            data.setCreateDept(null);
                            data.setCreateBy(null);
                            data.setUpdateBy(null);
                            set.add(tenantId);
                            saveDataList.add(data);
                        }
                    }
                }
            }
        }
        TenantHelper.ignore(() -> {
            if (CollUtil.isNotEmpty(saveTypeList)) {
                dictTypeMapper.insertBatch(saveTypeList);
            }
            if (CollUtil.isNotEmpty(saveDataList)) {
                dictDataMapper.insertBatch(saveDataList);
            }
        });
        for (String tenantId : set) {
            TenantHelper.dynamic(tenantId, () -> CacheUtils.clear(CacheNames.DICT));
        }
    }

}
